package mmc.action.skill;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import mmc.Client;
import mmc.EngineObject;
import mmc.action.Action;
import mmc.action.TargetException;
import mmc.action.buff.*;
import mmc.networking.EndCast;
import mmc.networking.UseAction;
import mmc.objects.Mob;
import mmc.objects.RemotePlayer;
import mmc.server.Player;
import mmc.server.RemoteClient;
import mmc.stats.StandardStats;
import mmc.ui.Text;

import static org.lwjgl.input.Keyboard.CHAR_NONE;;

public abstract class Skill extends Action {
	public static List<Skill> skills = new ArrayList<Skill>();
	
	public static Map<Skill, Float> cooldowns = new HashMap<Skill, Float>();
	public static Skill actualCast;
	
	public float cooldown=0;			//cooldown in seconds - default 0sec for no cooldown
	public float casttime=0;			//casttime in seconds - default 0sec for instant
	public float start;					//start cast timer
	public boolean enable=true;			//has cooldown?
	public boolean friendly = true;		//heal or dmg?
	public boolean needTarget = true;	//need a target for the skill?
	public int targetId = -1;			//target id
	public float range = 10f;			//range of the skill
	
	public static boolean globalCD;
	public static float globalTimer = mmc.Constants.GLOBAL_CD_TIME;
	public boolean freeFromGCD = false;
	
	public int energy = 20;
	
	static {
		skills.add(new DebugCommand());
		skills.add(new Mark());
		skills.add(new SteadyShot());
		skills.add(new Heal());
		skills.add(new Bomb());
		skills.add(new RocketLauncher());
		skills.add(new Healpot());
		skills.add(new Portal());
		skills.add(new Schock());
		skills.add(new Consecration());
		skills.add(new Taunt());
		skills.add(new Wuchs());
		skills.add(new Power());
		skills.add(new Ball());
	}
	
	public Skill(String name, String textureName, float cooldown) {
		super(name, textureName, CHAR_NONE);
		this.cooldown = cooldown;
	}
	
	public abstract void endCast(RemoteClient client);
	
	public boolean startCast(){
		if(actualCast == null){
			actualCast = this;
			actualCast.start = 0;
			return true;
		}
		else{
			Text.addMessage("Not available");
			return false;
		}
	}
	
	public static void checkCast(float dt){
		if(actualCast != null)		
			actualCast.start += dt;
	}
	
	public static void interruptCast(){
		actualCast = null;
	}
	
	public static void update(float dt){
		checkCast(dt);
		checkCooldowns(dt);
		
		if(globalCD)
		{
			globalTimer -= dt;
			if(globalTimer <= 0)
			{
				globalCD = false;
			}
		}
	}
	
	public void activateCooldown(){
		globalCD = true;
		globalTimer = mmc.Constants.GLOBAL_CD_TIME;
		
		if(this.cooldown > 0){
			this.enable = false;
			synchronized (cooldowns) {
				cooldowns.put(this, cooldown);	
			}
		}
	}
	
	public static void checkCooldowns(float dt){
		if(cooldowns.size() > 0){
			Map<Skill, Float> cc_copy = new HashMap<Skill, Float>();
			cc_copy.putAll(cooldowns);
			
			synchronized (cc_copy) {
				for(Map.Entry<Skill, Float> entry : cc_copy.entrySet()){
					float help = entry.getValue()-dt;
					if(help < 0){
						cooldowns.remove(entry.getKey());
						entry.getKey().enable = true;
					}
					else
						cooldowns.put(entry.getKey(), help);			
				}
			}
		}
		
		Buff.checkBuffs(dt);
	}
	
	public boolean isAvailable()
	{
		//in inventory?		
		
		if(!globalCD){
			if(this.energy <= Client.client.getScene().player.stats.getValue(StandardStats.EN)){
				if(this.enable)
				{
					if(actualCast == null)
					{
						if(needTarget){
							EngineObject tar = Client.client.getScene().player.target;
							if(tar != null){
								if(Client.client.getScene().player.inRange(tar, range))	//in range?
								{
									if((friendly && tar instanceof RemotePlayer) || (!friendly && tar instanceof Mob))
									{
										return true;
									}
									else
									{
										Text.addMessage("Wrong target");
										return false;
									}
								}
								else{
									Text.addMessage("Target not in range");
									return false;
								}
							}
							else{
								Text.addMessage("No target");
								return false;
							}
						}
						else{
							return true;
						}
					}
					else
					{
						Text.addMessage("Another action in progress");
						return false;
					}			
				}
				else
				{
					Text.addMessage("Skill not ready");
					return false;
				}		
			}
			else{
				Text.addMessage("Not enough energy");
				return false;
			}
		}
		else{
			Text.addMessage("Wait for global cooldown");
			return false;			
		}
	
	}
	
	@Override
	public void performOnClient(){
		if(isAvailable())
			Client.client.getScene().getContext().post(new UseAction(this.getClass().getName(), (Client.client.getScene().getPlayer().target==null)?-1:Client.client.getScene().getPlayer().target.id));			
	}
	
	@Override
	public void performOnServer(RemoteClient client, int targetId) throws TargetException {
		this.targetId = targetId;
		
		if(possible(client))
		{
			/*if(this.casttime == 0){						//instant
				client.getPlayer().currentCast = this;
				client.getPlayer().endCast();
			}
			else*/
				client.getPlayer().startCast(this);
		}
		else
		{
			client.post(new EndCast(false));
		}
	}
	
	public boolean possible(RemoteClient client)
	{
		Player player1 = client.getPlayer();
			
		if(player1.cooldown.containsKey(this.getClass().getName())){
			return false;
		}
		else{
			try {
				if(needTarget){
					EngineObject tar = (EngineObject) resolveTarget(client, targetId);
					
					if(player1.inRange(tar, 10)){
						return true;
					}
					else{
						return false;
					}
				}
				else{
					return true;
				}
			} catch (TargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}				
	}

	public static int randomDMG(int peak, int n){
		int damage = 0;
		
		int roll1 = rollDice(n,  peak);
		int roll2 = rollDice(n,  peak);
		damage = Math.max(roll1, roll2);
		
		//crit
		if(Math.random() <= 0.05f){
			System.out.println("crit");
			damage += rollDice(n, peak);
		}
		
		//System.out.println(damage);
		return damage;
	}
	
	public static int rollDice(int N, int s){
	    // Sum of N S-sided dice
	    int value = 0;
	    for (int i = 0; i < N; i++) {
	        value += Math.abs(new Random().nextInt()%s);
	    }
	    return value;
	}
}
